﻿using System;
using System.Collections.Generic;
using System.Web.Mvc;
using RESTController.ActionFilters;
using RESTController.Lib;

namespace RESTController
{
    /// <summary>
    /// A nested resource is defined to represent an collection of items as a child of a parent object.
    /// </summary>
    public abstract class NestedResourceController<TParent, TChild> : Controller
        where TChild : class
    {
        protected int pageIndex;

        /// <summary>
        /// Gets the number of items to show on a list page.
        /// </summary>
        protected virtual int PageSize
        {
            get { return RESTControllerSettings.Settings.PageSize; }
        }

        /// <summary>
        /// If true, successful new and edit operations will redirect to the index view. If false, successful new and edit operations will redirect to the show view.
        /// </summary>
        protected virtual bool RedirectToIndex
        {
            get { return RESTControllerSettings.Settings.RedirectToIndex; }
        }

        protected virtual bool UsePaging
        {
            get { return RESTControllerSettings.Settings.UsePaging; }
        }

        public virtual ActionResult Count(object parentId)
        {
            OnCountRequested(parentId);
            var count = GetChildCount(parentId);
            if (RESTControllerSettings.Settings.AllowJsonGet)
            {
                return Json(new { count }, JsonRequestBehavior.AllowGet);
            }
            return Json(new { count });
        }

        [UseDeleteMethodAsConfigured]
        public virtual ActionResult Delete(object id)
        {
            try
            {
                OnDeleteRequested(id);
                DeleteChild(id);
                OnDeleteSuccessful(id);
            }
            catch (Exception ex)
            {
                OnDeleteError(ex);
            }

            if (Request.IsAjaxRequest())
            {
                // If an AJAX request was used, the author can decide what to do
                // from the view (probably do a window.reload).
                return new EmptyResult();
            }
            return View("DeleteSuccessful");
        }

        [HttpGet]
        public virtual ActionResult Edit(object id)
        {
            OnEditRequested(id);
            var model = GetChild(id);

            if (Request.IsAjaxRequest())
            {
                return PartialView("EditControl", model);
            }
            return View("Edit", model);
        }

        [UseEditMethodAsConfigured]
        public virtual ActionResult Edit(object id, TChild child)
        {
            try
            {
                OnEditRequested(id);
                if (ModelState.IsValid)
                {
                    var originalChild = GetChild(id);
                    UpdateResource(originalChild, child);
                    SaveChild(originalChild);
                    OnEditSuccessful(originalChild);

                    if (RedirectToIndex)
                    {
                        var parentId = GetParentId(originalChild);

                        if (Request.IsAjaxRequest())
                        {
                            return Index(parentId);
                        }
                        return RedirectToAction("Index", new { parentId });
                    }

                    // Not going to Index. Going to "Show" action instead.
                    if (Request.IsAjaxRequest())
                    {
                        return Show(id);
                    }
                    return RedirectToAction("Show", new { id });
                }

                OnEditModelStateInvalid(child);
                if (Request.IsAjaxRequest())
                {
                    return PartialView("EditControl", child);
                }
                return View("Edit", child);
            }
            catch (Exception exception)
            {
                OnEditError(exception);
                if (Request.IsAjaxRequest())
                {
                    return PartialView("EditControl", child);
                }
                return View("Edit", child);
            }  
        }

        public virtual ActionResult Index(object parentId)
        {
            OnIndexRequested(parentId);
            return List(parentId, 1);
        }

        public virtual ActionResult List(object parentId, int? page)
        {
            OnListRequested(page);
            var pageIndex = (page == null) ? 0 : page.Value - 1;
            var collection = GetChildren(parentId);
            ViewModel.ParentObject = GetParent(parentId);
            if (UsePaging)
            {
                var model = new PagedList<TChild>(collection, pageIndex, RESTControllerSettings.Settings.PageSize);
                if (Request.IsAjaxRequest())
                {
                    return PartialView("ListControl", model);
                }
                return View("List", model);
            }
            if (Request.IsAjaxRequest())
            {
                return PartialView("ListControl", collection);
            }
            return View("List", collection);
        }

        [HttpGet]
        public virtual ActionResult New(object parentId)
        {
            OnNewRequested(parentId);
            var child = GetDefaultChild(parentId);
            ViewModel.ParentObject = GetParent(parentId);
            if (Request.IsAjaxRequest())
            {
                return PartialView("NewControl", child);
            }
            return View("New", child);
        }

        [UseNewMethodAsConfigured]
        public virtual ActionResult New(object parentId, TChild child)
        {
            try
            {
                OnNewRequested(parentId);
                if (ModelState.IsValid)
                {
                    var id = AddChild(parentId, child);
                    OnNewSuccessful(parentId, child);

                    if (RedirectToIndex)
                    {
                        if (Request.IsAjaxRequest())
                        {
                            return Index(parentId);
                        }
                        return RedirectToAction("Index", new { parentId });
                    }
                    // Don't redirect to index. Go to the "show" action instead.
                    if (Request.IsAjaxRequest())
                    {
                        return Show(id);
                    }
                    return RedirectToAction("Show", new { id });
                }

                // Model state is not valid.
                OnNewModelStateInvalid(child);
                ViewModel.ParentObject = GetParent(parentId);
                if (Request.IsAjaxRequest())
                {
                    return PartialView("NewControl", child);
                }
                return View("New", child);
            }
            catch (Exception ex)
            {
                OnNewError(ex);
                ViewModel.ParentObject = GetParent(parentId);
                if (Request.IsAjaxRequest())
                {
                    return PartialView("NewControl", child);
                }
                return View("New", child);
            }
        }

        public virtual ActionResult Show(object id)
        {
            OnShowRequested(id);
            var child = GetChild(id);

            if (Request.IsAjaxRequest())
            {
                return PartialView("ShowControl", child);
            }
            return View("Show", child);
        }

        #region Data operations

        /// <summary>
        /// Add a the child object to a parent object with the given parent ID.
        /// </summary>
        /// <returns>ID of the newly added child object.</returns>
        protected abstract object AddChild(object parentId, TChild child);
        
        protected abstract void DeleteChild(object id);

        protected abstract int GetChildCount(object parentId);
        
        protected abstract TChild GetDefaultChild(object parentId);
        
        protected abstract TChild GetChild(object id);
        
        protected abstract IEnumerable<TChild> GetChildren(object parentId);
        
        /// <summary>
        /// Calculate the number of pages.
        /// </summary>
        /// <param name="childCount">Number of child objects.</param>
        /// <returns>Number of pages.</returns>
        protected virtual int GetPageCount(int childCount)
        {
            return PageCountCalculator.CalculateNumberOfPages(PageSize, childCount);
        }
        
        protected abstract TParent GetParent(object parentId);
        
        protected abstract object GetParentId(TChild child);
        
        protected abstract void SaveChild(TChild child);

        #endregion

        #region Event extension methods

        protected virtual void OnCountRequested(object parentId)
        {
        }

        protected virtual void OnDeleteError(Exception exception)
        {
        }

        protected virtual void OnDeleteRequested(object id)
        {
        }

        protected virtual void OnDeleteSuccessful(object id)
        {            
        }

        protected virtual void OnEditError(Exception exception)
        {            
        }

        protected virtual void OnEditModelStateInvalid(TChild child)
        {            
        }

        protected virtual void OnEditRequested(object id)
        {
        }

        protected virtual void OnEditSuccessful(TChild child)
        {
        }

        protected virtual void OnIndexRequested(object parentId)
        {            
        }

        protected virtual void OnListRequested(int? page)
        {
            pageIndex = (page == null) ? 0 : page.Value - 1;
        }

        protected virtual void OnNewError(Exception exception)
        {
        }

        protected virtual void OnNewModelStateInvalid(TChild child)
        {            
        }

        protected virtual void OnNewRequested(object parentId)
        {
        }

        protected virtual void OnNewSuccessful(object parentId, TChild child)
        {
            
        }

        protected virtual void OnShowRequested(object id)
        {
        }

        protected virtual void UpdateResource(TChild original, TChild form)
        {
            UpdateModel(original);
        }

        #endregion
    }
}
